package ru.usu.gv.utils.imports;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import ru.usu.gv.core.GraphCanvas;
import ru.usu.gv.gui.actions.ChangeGraphDirectionAction;
import ru.usu.gv.styles.StyleFacade;
import ru.usu.gv.uicomponents.UIEdge;
import ru.usu.gv.uicomponents.UIGraph;
import ru.usu.gv.uicomponents.UIVertex;
import ru.usu.gv.utils.RandomUtilities;
import ru.usu.gv.utils.XmlUtil;

import javax.media.j3d.Transform3D;
import javax.vecmath.Point3d;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 @author Sergey Pupyrev 
 Jun 25, 2006
 */
public class GraphImporter implements IGraphXmlConstants
{
	public static UIGraph loadGraph(String path) throws XmlParseException
	{
		try
		{
			System.out.print("Start importing graph (" + path + ")...");

			Node graphRootElement = readRootElement(path, GRAPH_NODE);
			//directed
			boolean directed = XmlUtil.readBooleanAttribute(graphRootElement, GRAPH_DIRECTED_ATTRIBUTE, true);
			new ChangeGraphDirectionAction(directed).execute();

			String id = XmlUtil.readStringAttribute(graphRootElement, GRAPH_ID);
			UIGraph graph = new UIGraph(id);
			Map<String, UIVertex> idToVertex = new HashMap();

			//vertexes
			NodeList nodes = XmlUtil.getChildsByTagName(graphRootElement, VERTEX_NODE);
			importNodes(graph, directed, nodes, idToVertex);

			//edges
			nodes = XmlUtil.getChildsByTagName(graphRootElement, EDGE_NODE);
			importEdges(graph, directed, nodes, idToVertex);

			//view
			importView(path);

			System.out.println("ok");
			return graph;
		}
		catch (XmlParseException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new XmlParseException(e);
		}
	}

	public static Node readRootElement(String path, String rootNode) throws Exception, XmlParseException
	{
		return readRootElement(path, rootNode, true);
	}

	private static Node readRootElement(String path, String rootNode, boolean mandatory) throws Exception, XmlParseException
	{
		Document root = XmlUtil.loadXmlFile(path);
		NodeList rootElemets = root.getElementsByTagName(rootNode);
		if (rootElemets.getLength() != 1)
		{
			if (mandatory)
				throw new XmlParseException("Wrong number of root elemts: " + rootElemets.getLength());
			else
				return null;
		}
		return rootElemets.item(0);
	}

	private static void importView(String path) throws Exception
	{
		Node viewElementRoot = readRootElement(path, VIEW_NODE, false);
		if (viewElementRoot == null)
			return;

		double[] matrix = new double[16];
		for (int i = 0; i < matrix.length; i++)
		{
			NodeList nodeList = XmlUtil.getChildsByTagName(viewElementRoot, "transform" + String.valueOf(i));
			Node subNode = nodeList.item(0);
			matrix[i] = XmlUtil.readDoubleAttribute(subNode, "value");
		}

		Transform3D transform = new Transform3D();
		transform.set(matrix);
		GraphCanvas.getInstance().getTransformGroup().setTransform(transform);
	}

	private static void importNodes(UIGraph graph, boolean directed, NodeList nodes, Map<String, UIVertex> idToVertex) throws Exception
	{
		int count = nodes.getLength();
		for (int i = 0; i < count; i++)
		{
			Node node = nodes.item(i);
			String id = XmlUtil.readStringAttribute(node, VERTEX_ID);

			//point
			Point3d point;
			if (XmlUtil.existsAttribute(node, VERTEX_X))
			{
				point = readPoint3d(node);
			}
			else
			{
				point = RandomUtilities.generateRandomPoint();
			}

			//type
			String vertexTypeId = StyleFacade.getVertexType();
			if (XmlUtil.existsAttribute(node, VERTEX_TYPE))
			{
				vertexTypeId = XmlUtil.readStringAttribute(node, VERTEX_TYPE);
			}
			UIVertex v = new UIVertex(point, vertexTypeId);

			//label
			if (XmlUtil.existsAttribute(node, VERTEX_LABEL))
			{
				v.setLabel(XmlUtil.readStringAttribute(node, VERTEX_LABEL));
			}
			else
			{
				v.setLabel(id);
			}

			//color
			if (XmlUtil.existsAttribute(node, VERTEX_COLOR))
			{
				int color = XmlUtil.readIntAttribute(node, VERTEX_COLOR);
				v.getViewItem().setColor(new Color(color));
			}

			graph.addVertex(v);
			idToVertex.put(id, v);
		}
	}

	private static void importEdges(UIGraph graph, boolean directed, NodeList nodes, Map<String, UIVertex> idToVertex) throws Exception, XmlParseException
	{
		int edgeCount = nodes.getLength();
		for (int i = 0; i < edgeCount; i++)
		{
			Node node = nodes.item(i);
			String begin, end;
			begin = XmlUtil.readStringAttribute(node, EDGE_BEGIN);
			end = XmlUtil.readStringAttribute(node, EDGE_END);

			UIVertex v1 = idToVertex.get(begin);
			UIVertex v2 = idToVertex.get(end);
			if (v1 == null)
				throw new XmlParseException("Can't find node with id='" + begin + "'");
			if (v2 == null)
				throw new XmlParseException("Can't find node with id='" + end + "'");

			UIEdge edge = readEdge(node, v1, v2);

			graph.addEdge(edge);
		}
	}

	private static UIEdge readEdge(Node node, UIVertex v1, UIVertex v2) throws Exception
	{
		UIEdge edge = null;
		//type
		if (XmlUtil.existsAttribute(node, EDGE_TYPE))
		{
			String type = XmlUtil.readStringAttribute(node, EDGE_TYPE);
			edge = new UIEdge(v1, v2, type);
		}
		else
		{
			edge = new UIEdge(v1, v2);
		}

		//color
		if (XmlUtil.existsAttribute(node, EDGE_COLOR))
		{
			int color = XmlUtil.readIntAttribute(node, EDGE_COLOR);
			edge.getViewItem().setColor(new Color(color));
		}
		//controlPoints
		edge.getViewItem().setControlPoints(readControlPoints(node));
		return edge;
	}

	private static Point3d[] readControlPoints(Node node) throws Exception
	{
		NodeList controlNodeList = XmlUtil.getChildsByTagName(node, EDGE_CONTROL_POINT);
		List<Point3d> points = new ArrayList<Point3d>();
		for (int j = 0; j < controlNodeList.getLength(); j++)
		{
			Node controlNode = controlNodeList.item(j);
			points.add(readPoint3d(controlNode));
		}

		return points.toArray(new Point3d[] {});
	}

	private static Point3d readPoint3d(Node node) throws Exception
	{
		double x = XmlUtil.readDoubleAttribute(node, VERTEX_X);
		double y = XmlUtil.readDoubleAttribute(node, VERTEX_Y);
		double z = XmlUtil.readDoubleAttribute(node, VERTEX_Z);
		return new Point3d(x, y, z);
	}

}
